#include "tv_meta.h"
#include "parse_stream.h"
#include <stack>
#include <sstream>

static int _gen_index = 0;
std::string _gen_scope_id() {
	std::stringstream ss;
	ss << "__gen_scope_";
	ss << _gen_index++;
	return ss.str();
}

tv_instructions_s::~tv_instructions_s() {
	int imabreakpoint = 0;
	++imabreakpoint;
}

void tv_meta_c::parse_file(char const * filename) {

	std::stack< std::shared_ptr< tv_instructions_s > > scopes;

	parse_stream_c ps;
	ps.begin_file(filename);
	int line_index = 0;
	while (ps.more()) {
		auto line = ps.read_line();
		++line_index; // note the increment here makes us index from 1 to match text editors

		parse_stream_c ps2(line.c_str());
		tv_line_s tvl;
		tvl.line_index = line_index;

		try {
			tvl.parse(line.c_str());
			if (tvl.null())
				continue;
			
			bool push_scope = true;
			std::string func_id;
			if (tvl.tokens[0].type == TT_IF) {

				tv_line_s facade;
				facade.tokens = tvl.tokens;
				facade.tokens.back().type = TT_FUNC_GEN;
				facade.raw = tvl.raw;
				facade.line_index = line_index;

				func_id = _gen_scope_id();
				facade.tokens.back().arg_str = func_id;
				scopes.top()->lines.push_back(facade);

			} else if ( tvl.tokens[0].type == TT_FUNC_ ) {
				func_id = tvl.tokens[0].to_string();
			} else if ( tvl.tokens[0].type == TT_COND_ ) {
				// empty
			} else push_scope = false;
			
			if (push_scope) {
				std::shared_ptr< tv_instructions_s > instructions = std::make_shared< tv_instructions_s >();
				instructions->header = tvl;
				scopes.push(instructions);

				if (tvl.tokens[0].type == TT_IF) {
					var_named_functions[func_id] = instructions;
				} else if (tvl.tokens[0].type == TT_FUNC_) {
					var_named_functions[func_id] = instructions;
					_add_entrypoints(instructions);
				} else if (tvl.tokens[0].type == TT_COND_) {
					if (var_conditions.size() < unsigned(tvl.tokens[0].arg_int + 1))
						var_conditions.resize(tvl.tokens[0].arg_int + 1);
					var_conditions[tvl.tokens[0].arg_int] = instructions;
				} else _ASSERT(0);

			} else {
				if (tvl.tokens[0].type == TT_SCOPE_CLOSE) {
					auto top_scope = scopes.top();
					scopes.pop();
				} else {

					bool requires_scope = false;
					if (tvl.tokens[0].type == TT_ON_IDLE) {
						_ASSERT(tvl.tokens[1].type == TT_COND_);
						var_idle.push_back(tvl.tokens[1].arg_int);
					} else if (tvl.tokens[0].type == TT_ON_STEP) {
						_ASSERT(tvl.tokens[1].type == TT_COND_);
						var_always.push_back(tvl.tokens[1].arg_int);
					} else requires_scope = true;

					if (scopes.empty() == requires_scope)
						throw std::exception("this command can only be used at global scope");

					if (requires_scope) {
						scopes.top()->lines.push_back(tvl);
					}					
				}
			}
		} catch (std::exception ex) {
			throw ex;
		}
	}
	if (0 != scopes.size())
		throw std::exception("dangling scope");
}
void tv_meta_c::_add_entrypoints(std::shared_ptr< tv_instructions_s > func) {

	auto & toks = func->header.tokens;
	_ASSERT(toks[0].type == TT_FUNC_);
	
	if (TT_RUNS_ON == toks[1].type) {
		for (int i = 2; toks[i].type == TT_THREAD_; ++i) {
			var_entry_points[ toks[i].arg_int ].push_back( func );
		}
	}
}